#include "namegiverengine.h"
#include "../database.h"
#include "../random.h"
#include <QApplication>

const char* NameGiverEngine::ARG_RACE = "race";
const char* NameGiverEngine::ARG_CREATION_POINTS = "creation_points";
const int NameGiverEngine::ARG_CREATION_POINTS_DEFAULT = 66;
const char* NameGiverEngine::ARG_LEGEND_POINTS = "legend_points";
const int NameGiverEngine::ARG_LEGEND_POINTS_DEFAULT = 0;
const char* NameGiverEngine::ARG_MAX_LANGUAGES = "max_languages";
// TODO
const int NameGiverEngine::ARG_MAX_LANGUAGES_DEFAULT = 3;

const char* NameGiverEngine::ARG_RAISE_KNOWN_SKILL_PROBABILITY = "raise_known_skill_probability";
const double NameGiverEngine::ARG_RAISE_KNOWN_SKILL_PROBABILITY_DEFAULT = 0.5;
const char* NameGiverEngine::ARG_LEARN_NEW_SKILL_PROBABILITY = "learn_new_skill_probability";
const double NameGiverEngine::ARG_LEARN_NEW_SKILL_PROBABILITY_DEFAULT = 0.3;
// This is only effective if we previously chose to raise a skill.
const char* NameGiverEngine::ARG_PRIME_SKILLS = "prime_skills";
const char* NameGiverEngine::ARG_RAISE_PRIME_SKILL_PROBABILITY = "raise_prime_skill_probability";
const double NameGiverEngine::ARG_RAISE_PRIME_SKILL_PROBABILITY_DEFAULT = 0.6;


const char* NameGiverEngine::ARG_RAISE_ATTRIBUTE_PROBABILITY = "raise_attribute_probability";
const double NameGiverEngine::ARG_RAISE_ATTRIBUTE_PROBABILITY_DEFAULT = 0.2;
// This is only effective if we previously chose to raise an attribute.
const char* NameGiverEngine::ARG_PRIME_ATTRIBUTES = "prime_attributes";
const char* NameGiverEngine::ARG_RAISE_PRIME_ATTRIBUTE_PROBABILITY = "raise_prime_attribute_probability";
const double NameGiverEngine::ARG_RAISE_PRIME_ATTRIBUTE_PROBABILITY_DEFAULT = 0.8;

NameGiverEngine::NameGiverEngine()
  : Engine()
{
  reset();
}

void NameGiverEngine::init(const QVariantMap &args)
{
  if (_nameGiver == nullptr) _nameGiver = new NameGiver();
  _character = _nameGiver;

  Q_ASSERT(args.contains(ARG_RACE));
  __ENGINE_CHECKARG__(ARG_CREATION_POINTS,_creationPoints,Int);
  __ENGINE_CHECKARG__(ARG_LEGEND_POINTS,_legendPoints,Int);
  __ENGINE_CHECKARG__(ARG_MAX_LANGUAGES,_maxLanguages,Int);
  __ENGINE_CHECKARG__(ARG_RAISE_KNOWN_SKILL_PROBABILITY,_raiseKnownSkillProbability,Double);
  __ENGINE_CHECKARG__(ARG_LEARN_NEW_SKILL_PROBABILITY,_learnNewSkillProbability,Double);
  __ENGINE_CHECKARG__(ARG_RAISE_ATTRIBUTE_PROBABILITY,_raiseAttributeProbability,Double);
  __ENGINE_CHECKARG__(ARG_RAISE_PRIME_SKILL_PROBABILITY,_raisePrimeSkillProbability,Double);
  __ENGINE_CHECKARG__(ARG_RAISE_PRIME_ATTRIBUTE_PROBABILITY,_raisePrimeAttributeProbability,Double);
  if (args.contains(ARG_PRIME_SKILLS))
  {
    QStringList primeSkillNames = args[ARG_PRIME_SKILLS].toStringList();
    foreach(const Skill& s, Database::skills())
    {
      if (primeSkillNames.contains(s.name())) _primeSkills << Database::skill(s);
      else _otherSkills << Database::skill(s);
    }
  }
  else _otherSkills = Database::skills();

  if (args.contains(ARG_PRIME_ATTRIBUTES))
  {
    QVariantList primeAttributesVariants = args[ARG_PRIME_ATTRIBUTES].toList();
    foreach(const QVariant& var, primeAttributesVariants) _primeAttributes << var.toInt();
    foreach(ed::Attribute a, ed::Attribute::values())
    {
      if (!_primeAttributes.contains(a)) _otherAttributes << a;
    }
  }
  else _otherAttributes = ed::Attribute::values();
  _nameGiver->set_race(args[ARG_RACE].toInt());
  _nameGiver->set_legendPoints(_legendPoints);
}

void NameGiverEngine::start()
{
  _log << "======= CREATION DES VALEURS DE BASE ======" << _log.endl;
  createBase();
  refreshLists();
  _log << "======= DEPENSE DES POINTS DE LEGENDE ======" << _log.endl;
  while (canSpendLegendPoints())
  {
    QApplication::processEvents();
    spendLegendPoints();
    refreshLists();
  }
  _log << "Points de légende restants: " << _nameGiver->legendPoints() << _log.endl;
  _log << "======= PROCESSUS DE CREATION TERMINE =======" << _log.endl;
}

void NameGiverEngine::reset()
{
  _nameGiver = nullptr;
  _attributeAugmentations.clear();
  foreach(ed::Attribute a, ed::Attribute::values()) _attributeAugmentations[a] = 0;
  _primeAttributes.clear();
  _otherAttributes.clear();
  _primeSkills.clear();
  _otherSkills.clear();
  _knownPrimeSkills.clear();
  _knownOtherSkills.clear();
}

// Character base
void NameGiverEngine::createBase()
{
  createBaseAttributes();
  createBaseSkills();
}
void NameGiverEngine::createBaseAttributes()
{
  _log << "[Attributs] Création des valeurs de base" << _log.endl;
  _log << "[Attributs] Application des minimats raciaux" << _log.endl;
  foreach(ed::Attribute a, ed::Attribute::values()) _nameGiver->setAttribute(a,2);
  if (_nameGiver->race() == ed::Race_Obsidien) _nameGiver->setAttribute(ed::Attr_FOR,9);
  else if (_nameGiver->race() == ed::Race_Troll)
  {
    _nameGiver->setAttribute(ed::Attr_FOR,7);
    _nameGiver->setAttribute(ed::Attr_CON,9);
  }
  // update creation points
  foreach(ed::Attribute a, ed::Attribute::values()) _creationPoints += Database::attributeCost(_nameGiver->attributeValue(a));
  _log << "[Attributs] " << _creationPoints << " points restants." << _log.endl;

  QList<ed::Attribute> availablePrimeAttributes = _primeAttributes;
  QList<ed::Attribute> availableOtherAttributes = _otherAttributes;

  while (!availableOtherAttributes.isEmpty() || !availablePrimeAttributes.isEmpty())
  {
    ed::Attribute a;
    if (!availablePrimeAttributes.isEmpty() && Random::check(_raisePrimeAttributeProbability)) a = availablePrimeAttributes[Random::get<int>(0,availablePrimeAttributes.size()-1)];
    else a = availableOtherAttributes[Random::get<int>(0,availableOtherAttributes.size()-1)];

    int oldValue = _nameGiver->attributeValue(a);

    int newValue = oldValue+1;

    int oldCost = Database::attributeCost(oldValue);
    int newCost = Database::attributeCost(newValue);

    if (_creationPoints - oldCost + newCost >= 0)
    {
      _log << "[Attributs] Passage de l'attribut " << a << " à " << newValue << _log.endl;
      _nameGiver->setAttribute(a,newValue);
      _creationPoints -= oldCost;
      _creationPoints += newCost;
      _log << "[Attributs] " << _creationPoints << " points restants" << _log.endl;
      if (newValue == 18)
      {
        availablePrimeAttributes.removeAll(a);
        availableOtherAttributes.removeAll(a);
      }
    }
    else
    {
      availablePrimeAttributes.removeAll(a);
      availableOtherAttributes.removeAll(a);
    }
  }

  _log << "[Attributs] Application des modificateurs raciaux" << _log.endl;
  foreach(ed::Attribute a, ed::Attribute::values())
  {
    int baseValue = _nameGiver->attributeValue(a);
    int modifier = Database::racialModifier(_nameGiver->race(),a);

    _log << a << " = " << baseValue << " + " << modifier << " = " << (baseValue+modifier) << _log.endl;
    _nameGiver->setAttribute(a,baseValue+modifier);
  }
}

// Legend points
void NameGiverEngine::refreshLists()
{
  foreach(ed::Attribute a, _attributeAugmentations.keys())
  {
    if (_attributeAugmentations[a] == Database::MAXIMUM_ATTRIBUTE_AUGMENTATION
        || Database::attributeLegendCost(_attributeAugmentations[a]+1) > _nameGiver->legendPoints())
    {
      _attributeAugmentations.remove(a);
      _primeAttributes.removeAll(a);
      _otherAttributes.removeAll(a);
    }
  }

  if (_nameGiver->legendPoints() < Database::skillLegendCost(1))
  {
    _primeSkills.clear();
    _knownPrimeSkills.clear();
    _otherSkills.clear();
    _knownOtherSkills.clear();
  }
  foreach(const Skill& s, _nameGiver->skills().keys())
  {
    int rank = _nameGiver->skillRank(s);
    if (rank == Database::MAXIMUM_SKILL_RANK || _nameGiver->legendPoints() < Database::skillLegendCost(rank+1))
    {
      _knownPrimeSkills.removeAll(s);
      _knownOtherSkills.removeAll(s);
    }
  }
}

bool NameGiverEngine::canSpendLegendPoints()
{
  return !(_otherAttributes.isEmpty()
           && _primeAttributes.isEmpty()
           && _primeSkills.isEmpty()
           && _knownPrimeSkills.isEmpty()
           && _otherSkills.isEmpty()
           && _knownOtherSkills.isEmpty());
}

void NameGiverEngine::spendLegendPoints()
{
  if (!_attributeAugmentations.isEmpty() && Random::check(_raiseAttributeProbability))
  {
    raiseAttribute();
  }
  refreshLists();
  if (!_primeSkills.isEmpty() && Random::check(_raisePrimeSkillProbability))
  {
    Skill s = RL(_primeSkills);
    _nameGiver->decreaseLegendPoints(raiseSkill(s));
  }
  refreshLists();
  if (!_knownPrimeSkills.isEmpty() && Random::check(_raisePrimeSkillProbability))
  {
    Skill s = RL(_knownPrimeSkills);
    _nameGiver->decreaseLegendPoints(raiseSkill(s));
  }
  refreshLists();
  if (!_otherSkills.isEmpty() && Random::check(_learnNewSkillProbability))
  {
    Skill s = RL(_otherSkills);
    _nameGiver->decreaseLegendPoints(raiseSkill(s));
  }
  refreshLists();
  if (!_knownOtherSkills.isEmpty() && Random::check(_raiseKnownSkillProbability))
  {
    Skill s = RL(_knownOtherSkills);
    _nameGiver->decreaseLegendPoints(raiseSkill(s));
  }
}

void NameGiverEngine::raiseAttribute()
{
  ed::Attribute a;
  if (!_primeAttributes.isEmpty() && Random::check(_raisePrimeAttributeProbability)) a = _primeAttributes[Random::get<int>(0,_primeAttributes.size()-1)];
  else a = _otherAttributes[Random::get<int>(0,_otherAttributes.size()-1)];

  _attributeAugmentations[a]++;
  _nameGiver->decreaseLegendPoints(Database::attributeLegendCost(_attributeAugmentations[a]));
  _nameGiver->setAttribute(a,_nameGiver->attributeValue(a)+1);
  _log << "[Attributs] Augmentation de l'attribut " << a << " à la valeur " << _nameGiver->attributeValue(a) << _log.endl;
}

void NameGiverEngine::createBaseSkills()
{
  _log << "[Compétences] Création des valeurs de base" << _log.endl;
  raiseSkill(Database::skill("Art"));
  raiseSkill(Database::skill("Don des langues"));
  raiseSkill(Database::skill("Connaissance (1)"));
  raiseSkill(Database::skill("Connaissance (2)"));

  int creationPoints = 5;
  while (creationPoints > 0)
  {
    bool prime = Random::check(_raisePrimeSkillProbability);
    bool learn = Random::check(_learnNewSkillProbability);
    bool raise = Random::check(_raiseKnownSkillProbability);

    Skill s;
    if (prime && (!_primeSkills.isEmpty() || !_knownPrimeSkills.isEmpty()))
    {
      if (learn && !_primeSkills.isEmpty())
      {
        s = RL(_primeSkills);
      }
      else if (raise && !_knownPrimeSkills.isEmpty())
      {
        s = RL(_knownPrimeSkills);
      }
    }
    else
    {
      if (!_otherSkills.isEmpty() && learn)
      {
        s = RL(_otherSkills);
      }
      if (!_knownOtherSkills.isEmpty() && raise)
      {
        s = RL(_knownOtherSkills);
      }
    }

    if (s.isValid())
    {
      raiseSkill(s);
      --creationPoints;
    }
  }
}

int NameGiverEngine::learnNewSkill(const Skill& s)
{
  if (_primeSkills.contains(s))
  {
    _knownPrimeSkills << s;
    _primeSkills.removeAll(s);
  }
  else
  {
    _knownOtherSkills << s;
    _otherSkills.removeAll(s);
  }
  _nameGiver->setSkillRank(s,1);

  return Database::skillLegendCost(1);
}

int NameGiverEngine::raiseSkill(const Skill &s)
{
  Q_ASSERT(s.isValid());
  if (!_nameGiver->skills().contains(s)) learnNewSkill(s);
  else
  {
    _nameGiver->setSkillRank(s,_nameGiver->skillRank(s)+1);
    if (_nameGiver->skillRank(s) == Database::MAXIMUM_SKILL_RANK)
    {
      _knownOtherSkills.removeAll(s);
      _knownPrimeSkills.removeAll(s);
    }
  }
  _log << "[Compétences] Passage de la compétence " << s << " au rang " << _nameGiver->skillRank(s) << _log.endl;
  return Database::skillLegendCost(_nameGiver->skillRank(s));
}
